package be.rivendale.ghetto;

import be.rivendale.mathematics.Point;
import be.rivendale.mathematics.Triple;
import org.apache.commons.lang.Validate;

/**
 * Represents a model composed entirely of voxels aligned in a 3D grid.
 * This class supports loading voxel data from a V3A file, which
 * is the ASCII based file format used by the Voxel3D program.
 * <p>The file is not checked against the .v3a file extension. It is however checked against it's content.
 * Therefor a file passed must be a valid v3a file, but can be named arbitrary (including .txt to allow easy debugging
 * since it's an ASCII file format)</p>
 * <p>The format is not very advanced, storing raw voxel data without any form of compression.
 * It is however not complex and therefor easy to write a file loader for.</p>
 */
public class VoxelModel {
	/**
	 * A 3D array containing the voxel data in a grid.
	 * Typical for voxel layout, is that each individual voxel does <strong>NOT</strong> know
	 * about it's specific coordinates (unlike triangle meshes). Instead, the position of a voxel is indirectly
	 * contained in it's location in the grid. (like a pixel grid)
	 */
	private Voxel[][][] voxelData;

	/**
	 * The relative position of this voxel model in the actual 3D space.
	 * This is the corner of the axis aligned bounding box of this voxel grid with the smallest coordinates.
	 * In other words; the bottom left front point of the grid. 
	 */
	private Point bottomLeftFrontPoint;

	/**
	 * The width of this voxel model.
	 * This is the size of the voxel grid's bounding box in global coordinates.
	 */
	private double width;

	/**
	 * The height of this voxel model.
	 * This is the size of the voxel grid's bounding box in global coordinates.
	 */
	private double height;

	/**
	 * The depth of this voxel model.
	 * This is the size of the voxel grid's bounding box in global coordinates.
	 */
	private double depth;

	public VoxelModel(Point bottomLeftFrontPoint, Voxel[][][] voxelData, double width, double height, double depth) {
		this.bottomLeftFrontPoint = bottomLeftFrontPoint;
		this.voxelData = voxelData;
		this.width = width;
		this.height = height;
		this.depth = depth;
	}

	/**
	 * Returns the relative position of this voxel model in global 3D space.
	 * The point is the bottom left front point of the bounding box around the grid.
	 * @return The relative position of this voxel grid in 3D space.
	 */
	public Point getBottomLeftFrontPoint() {
		return bottomLeftFrontPoint;
	}

	/**
	 * Calculates and retrieves the relative position of the point with the largest coordinates.
	 * This is the point diagonally opposite to the {@link #getBottomLeftFrontPoint()}.
	 * @return The corner of the bounding box with the largest coordinates.
	 */
	public Point getTopRightBackPoint() {
		return getBottomLeftFrontPoint().add(new Triple(getWidth(), getHeight(), getDepth()));
	}

	/**
	 * Returns the resolution of this voxel model in the X dimension.
	 * @return The resolution or size of the X dimension.
	 */
	public int getXResolution() {
		return voxelData.length;
	}

	/**
	 * Returns the resolution of this voxel model in the Y dimension.
	 * @return The resolution or size of the Y dimension.
	 */
	public int getYResolution() {
		return voxelData[0].length;
	}

	/**
	 * Returns the resolution of this voxel model in the Z dimension.
	 * @return The resolution or size of the Z dimension.
	 */
	public int getZResolution() {
		return voxelData[0][0].length;
	}

	/**
	 * Returns the width dimension of this voxel model.
	 * @return The width in world coordinates.
	 */
	public double getWidth() {
		return width;
	}

	/**
	 * Returns the height dimension of this voxel model.
	 * @return The height in world coordinates.
	 */
	public double getHeight() {
		return height;
	}

	/**
	 * Returns the depth dimension of this voxel model.
	 * @return The depth in world coordinates.
	 */
	public double getDepth() {
		return depth;
	}

	/**
	 * Returns the depth of one voxel in the voxel grid in workd coordinates.
	 * This is obtained by dividing the depth of the entire voxel grid by the number of voxels.
	 * @return The depth of one voxel.
	 */
	public double getVoxelWidth() {
		return getWidth() / getXResolution();
	}

	/**
	 * Returns the depth of one voxel in the voxel grid in workd coordinates.
	 * This is obtained by dividing the depth of the entire voxel grid by the number of voxels.
	 * @return The depth of one voxel.
	 */
	public double getVoxelHeight() {
		return getHeight() / getYResolution();
	}

	/**
	 * Returns the depth of one voxel in the voxel grid in workd coordinates.
	 * This is obtained by dividing the depth of the entire voxel grid by the number of voxels.
	 * @return The depth of one voxel.
	 */
	public double getVoxelDepth() {
		return getDepth() / getZResolution();
	}

	/**
	 * Retrieves the color of the voxel located at the specified one-based coodinate indexes X, Y and Z
	 * @param oneBasedXCoordinate The X coordinate of the voxel to retrieve
	 * @param oneBasedYCoordinate The Y coorinate of the voxel to retrieve
	 * @param oneBasedZCoordinate The Z coordinate of the voxel to retrieve
	 * @return The color of the voxel at the specified coordinates.
	 */
	public Voxel getAt(int oneBasedXCoordinate, int oneBasedYCoordinate, int oneBasedZCoordinate) {
		String errorMessage = "Invalid one-based index";
		Validate.isTrue(oneBasedXCoordinate > 0 && oneBasedXCoordinate <= getXResolution(), errorMessage);
		Validate.isTrue(oneBasedYCoordinate > 0 && oneBasedYCoordinate <= getYResolution(), errorMessage);
		Validate.isTrue(oneBasedZCoordinate > 0 && oneBasedZCoordinate <= getZResolution(), errorMessage);
		return voxelData[oneBasedXCoordinate - 1][oneBasedYCoordinate - 1][oneBasedZCoordinate - 1];
	}
}
